/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package com.googlecode.bluetools.dom4j.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import com.googlecode.bluetools.dom4j.Branch;
import com.googlecode.bluetools.dom4j.Comment;
import com.googlecode.bluetools.dom4j.Element;
import com.googlecode.bluetools.dom4j.IllegalAddException;
import com.googlecode.bluetools.dom4j.Namespace;
import com.googlecode.bluetools.dom4j.Node;
import com.googlecode.bluetools.dom4j.ProcessingInstruction;
import com.googlecode.bluetools.dom4j.QName;

/**
 * <p>
 * <code>AbstractBranch</code> is an abstract base class for tree implementors to use for implementation inheritence.
 * </p>
 * 
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 * @version $Revision: 1.1 $
 */
public abstract class AbstractBranch extends AbstractNode implements Branch {
	protected static final int DEFAULT_CONTENT_LIST_SIZE = 5;

	public AbstractBranch() {
	}

	public boolean isReadOnly() {
		return false;
	}

	public boolean hasContent() {
		return nodeCount() > 0;
	}

	public List content() {
		List backingList = contentList();

		return new ContentListFacade(this, backingList);
	}

	public String getText() {
		List content = contentList();

		if (content != null) {
			int size = content.size();

			if (size >= 1) {
				Object first = content.get(0);
				String firstText = getContentAsText(first);

				if (size == 1) {
					// optimised to avoid StringBuffer creation
					return firstText;
				}
				else {
					StringBuffer buffer = new StringBuffer(firstText);

					for (int i = 1; i < size; i++) {
						Object node = content.get(i);
						buffer.append(getContentAsText(node));
					}

					return buffer.toString();
				}
			}
		}

		return "";
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param content DOCUMENT ME!
	 * 
	 * @return the text value of the given content object as text which returns the text value of CDATA, Entity or Text
	 *         nodes
	 */
	protected String getContentAsText(Object content) {
		if (content instanceof Node) {
			Node node = (Node) content;

			switch (node.getNodeType()) {
			case CDATA_SECTION_NODE:

				// case ENTITY_NODE:
			case ENTITY_REFERENCE_NODE:
			case TEXT_NODE:
				return node.getText();

			default:
				break;
			}
		}
		else if (content instanceof String) {
			return (String) content;
		}

		return "";
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param content DOCUMENT ME!
	 * 
	 * @return the XPath defined string-value of the given content object
	 */
	protected String getContentAsStringValue(Object content) {
		if (content instanceof Node) {
			Node node = (Node) content;

			switch (node.getNodeType()) {
			case CDATA_SECTION_NODE:

				// case ENTITY_NODE:
			case ENTITY_REFERENCE_NODE:
			case TEXT_NODE:
			case ELEMENT_NODE:
				return node.getStringValue();

			default:
				break;
			}
		}
		else if (content instanceof String) {
			return (String) content;
		}

		return "";
	}

	public String getTextTrim() {
		String text = getText();

		StringBuffer textContent = new StringBuffer();
		StringTokenizer tokenizer = new StringTokenizer(text);

		while (tokenizer.hasMoreTokens()) {
			String str = tokenizer.nextToken();
			textContent.append(str);

			if (tokenizer.hasMoreTokens()) {
				textContent.append(" "); // separator
			}
		}

		return textContent.toString();
	}

	public void setProcessingInstructions(List listOfPIs) {
		for (Iterator iter = listOfPIs.iterator(); iter.hasNext();) {
			ProcessingInstruction pi = (ProcessingInstruction) iter.next();
			addNode(pi);
		}
	}

	public Element addElement(String name) {
		Element node = getDocumentFactory().createElement(name);
		add(node);

		return node;
	}

	public Element addElement(String qualifiedName, String namespaceURI) {
		Element node = getDocumentFactory().createElement(qualifiedName, namespaceURI);
		add(node);

		return node;
	}

	public Element addElement(QName qname) {
		Element node = getDocumentFactory().createElement(qname);
		add(node);

		return node;
	}

	public Element addElement(String name, String prefix, String uri) {
		Namespace namespace = Namespace.get(prefix, uri);
		QName qName = getDocumentFactory().createQName(name, namespace);

		return addElement(qName);
	}

	// polymorphic node methods
	public void add(Node node) {
		switch (node.getNodeType()) {
		case ELEMENT_NODE:
			add((Element) node);

			break;

		case COMMENT_NODE:
			add((Comment) node);

			break;

		case PROCESSING_INSTRUCTION_NODE:
			add((ProcessingInstruction) node);

			break;

		default:
			invalidNodeTypeAddException(node);
		}
	}

	public boolean remove(Node node) {
		switch (node.getNodeType()) {
		case ELEMENT_NODE:
			return remove((Element) node);

		case COMMENT_NODE:
			return remove((Comment) node);

		case PROCESSING_INSTRUCTION_NODE:
			return remove((ProcessingInstruction) node);

		default:
			invalidNodeTypeAddException(node);

			return false;
		}
	}

	// typesafe versions using node classes
	public void add(Comment comment) {
		addNode(comment);
	}

	public void add(Element element) {
		addNode(element);
	}

	public void add(ProcessingInstruction pi) {
		addNode(pi);
	}

	public boolean remove(Comment comment) {
		return removeNode(comment);
	}

	public boolean remove(Element element) {
		return removeNode(element);
	}

	public boolean remove(ProcessingInstruction pi) {
		return removeNode(pi);
	}

	public Element elementByID(String elementID) {
		for (int i = 0, size = nodeCount(); i < size; i++) {
			Node node = node(i);

			if (node instanceof Element) {
				Element element = (Element) node;
				String id = elementID(element);

				if ((id != null) && id.equals(elementID)) {
					return element;
				}
				else {
					element = element.elementByID(elementID);

					if (element != null) {
						return element;
					}
				}
			}
		}

		return null;
	}

	public void appendContent(Branch branch) {
		for (int i = 0, size = branch.nodeCount(); i < size; i++) {
			Node node = branch.node(i);
			add((Node) node.clone());
		}
	}

	public Node node(int index) {
		Object object = contentList().get(index);

		if (object instanceof Node) {
			return (Node) object;
		}

		if (object instanceof String) {
			return getDocumentFactory().createText(object.toString());
		}

		return null;
	}

	public int nodeCount() {
		return contentList().size();
	}

	public int indexOf(Node node) {
		return contentList().indexOf(node);
	}

	public Iterator nodeIterator() {
		return contentList().iterator();
	}

	// Implementation methods

	/**
	 * DOCUMENT ME!
	 * 
	 * @param element DOCUMENT ME!
	 * 
	 * @return the ID of the given <code>Element</code>
	 */
	protected String elementID(Element element) {
		// XXX: there will be other ways of finding the ID
		// XXX: should probably have an IDResolver or something
		return element.attributeValue("ID");
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return the internal List used to manage the content
	 */
	protected abstract List contentList();

	/**
	 * A Factory Method pattern which creates a List implementation used to store content
	 * 
	 * @return DOCUMENT ME!
	 */
	protected List createContentList() {
		return new ArrayList(DEFAULT_CONTENT_LIST_SIZE);
	}

	/**
	 * A Factory Method pattern which creates a List implementation used to store content
	 * 
	 * @param size DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	protected List createContentList(int size) {
		return new ArrayList(size);
	}

	/**
	 * A Factory Method pattern which creates a BackedList implementation used to store results of a filtered content
	 * query.
	 * 
	 * @return DOCUMENT ME!
	 */
	protected BackedList createResultList() {
		return new BackedList(this, contentList());
	}

	/**
	 * A Factory Method pattern which creates a BackedList implementation which contains a single result
	 * 
	 * @param result DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	protected List createSingleResultList(Object result) {
		BackedList list = new BackedList(this, contentList(), 1);
		list.addLocal(result);

		return list;
	}

	/**
	 * A Factory Method pattern which creates an empty a BackedList implementation
	 * 
	 * @return DOCUMENT ME!
	 */
	protected List createEmptyList() {
		return new BackedList(this, contentList(), 0);
	}

	protected abstract void addNode(Node node);

	protected abstract void addNode(int index, Node node);

	protected abstract boolean removeNode(Node node);

	/**
	 * Called when a new child node has been added to me to allow any parent relationships to be created or events to be
	 * fired.
	 * 
	 * @param node DOCUMENT ME!
	 */
	protected abstract void childAdded(Node node);

	/**
	 * Called when a child node has been removed to allow any parent relationships to be deleted or events to be fired.
	 * 
	 * @param node DOCUMENT ME!
	 */
	protected abstract void childRemoved(Node node);

	/**
	 * Called when the given List content has been removed so each node should have its parent and document
	 * relationships cleared
	 */
	protected void contentRemoved() {
		List content = contentList();

		for (int i = 0, size = content.size(); i < size; i++) {
			Object object = content.get(i);

			if (object instanceof Node) {
				childRemoved((Node) object);
			}
		}
	}

	/**
	 * Called when an invalid node has been added. Throws an {@link IllegalAddException}.
	 * 
	 * @param node DOCUMENT ME!
	 * 
	 * @throws IllegalAddException DOCUMENT ME!
	 */
	protected void invalidNodeTypeAddException(Node node) {
		throw new IllegalAddException("Invalid node type. Cannot add node: " + node + " to this branch: " + this);
	}
}

/*
 * Redistribution and use of this software and associated documentation ("Software"), with or without modification, are
 * permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions must also contain a
 * copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived from this Software without prior written
 * permission of MetaStuff, Ltd. For written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may "DOM4J" appear in their names without prior
 * written permission of MetaStuff, Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
